#pragma once

#include "LockGuard.hpp"
#include <cstdarg>
#include <cstdio>
#include <ctime>
#include <fstream>
#include <iostream>
#include <pthread.h>
#include <string>
#include <sys/types.h>
#include <unistd.h>
// 往显示器打印.往文件中写入

// 全局变量：控制日志输出方式
bool gIsSave = false; // false表示输出到屏幕，true表示输出到文件
const std::string logname = "log.txt"; // 日志文件名

// 1. 日志是由等级的
// 定义日志级别枚举，从低到高依次为：调试、信息、警告、错误、致命错误
enum Level {
  DEBUG = 0, // 调试信息，用于开发调试
  INFO,      // 一般信息，用于记录程序运行状态
  WARNING,   // 警告信息，表示潜在问题
  ERROR,     // 错误信息，表示程序错误
  FATAL      // 致命错误，可能导致程序崩溃
};

// 将日志内容保存到指定文件
// @param filename: 目标文件名
// @param message: 要保存的日志消息
void SaveFile(const std::string &filename, const std::string &message) {
  // 1. 创建并打开文件流对象
  // std::ofstream out(filename, std::ios::app);
  // - filename: 字符串，表示要打开或创建的文件的路径和名称。
  //             std::ofstream 的构造函数可以接受 std::string 对象 (C++11
  //             及以后) 或 const char*。这里是 std::string，所以是安全的。
  // - std::ios::app: 打开模式标志。
  //   - 效果:
  //     a. 如果名为 `filename` 的文件不存在，系统会尝试创建一个新的空文件。
  //     b. 如果文件已存在，文件指针会定位到文件的当前末尾。
  //     c. 所有后续的写入操作 (通过 `out << ...`) 都会从这个末尾位置开始，
  //        从而将新的 `message` 追加到文件原有内容的后面，而不会覆盖旧内容。
  //   - 隐含 `std::ios::out`: `std::ios::app` 模式通常隐含了输出能力。
  //     显式写作 `std::ios::out | std::ios::app` 也是可以的，意义相同。
  std::ofstream out(filename, std::ios::app); // 以追加模式打开文件
  // 2. 检查文件是否成功打开
  // if (!out.is_open())
  // - out.is_open() 返回一个布尔值。如果文件成功打开并与流 `out` 关联，则返回
  // true。
  // - 如果打开失败 (例如，路径无效、没有写权限、磁盘已满等)，则返回 false。
  // - `!out.is_open()` 因此在打开失败时为 true。
  if (!out.is_open()) {
    return;
  }
  // 3. 向文件写入消息
  // out << message;
  // - 使用流插入操作符 `<<` 将 `message` 字符串的内容写入到与 `out`
  // 关联的文件中。
  // - 由于是以 std::ios::app 模式打开，写入操作会在文件的末尾进行。
  // - 注意：这里没有显式地写入换行符 (`std::endl` 或 `\n`)。
  //   如果期望每条日志消息占据新的一行，那么 `message`
  //   字符串本身应该包含换行符， 或者在写入时添加它，例如：`out << message <<
  //   std::endl;` `std::endl` 不仅插入换行符，还会刷新 (flush) 输出缓冲区。
  out << message;
  // 4. 关闭文件 (可选但推荐)
  // out.close();
  // - 显式关闭文件。这会确保所有缓冲在内存中的数据被刷新到磁盘文件，
  //   并释放与该文件关联的系统资源。
  // - 如果不显式调用 `close()`，当 `out` 对象离开作用域（即 `SaveFile`
  // 函数返回时），
  //   `std::ofstream` 的析构函数也会自动调用 `close()`。
  // - 显式关闭的好处：
  //   a. 可以更早地检测到关闭时可能发生的错误（虽然 `close()`
  //   本身通常不被设计为报告所有类型的写入错误，错误主要在写入操作时通过流状态反映）。
  //   b. 如果之后需要立即对同一个文件进行其他操作（例如，用 `ifstream`
  //   读取），确保文件已关闭并刷新是重要的。 c. 代码意图更清晰。
  out.close();
}

// 将日志级别转换为对应的字符串
// @param level: 日志级别枚举值
// @return: 对应的日志级别字符串
std::string LevelToString(int level) {
  switch (level) {
  case DEBUG:
    return "Debug";
  case INFO:
    return "Info";
  case WARNING:
    return "Warning";
  case ERROR:
    return "Error";
  case FATAL:
    return "Fatal";
  default:
    return "Unknown";
  }
}

// 获取当前时间的格式化字符串
// @return: 格式化的时间字符串，格式为：年-月-日 时:分:秒
std::string GetTimeString() {
  // 1. 获取日历时间 (Calendar Time)
  // time_t curr_time = time(nullptr);
  // - time_t: 通常是一个整数类型 (如 long int 或 long long int)，
  //           用于表示从某个固定时间点 (称为纪元，Epoch) 到现在的秒数。
  //           POSIX 系统中，纪元通常是 1970-01-01 00:00:00 UTC (协调世界时)。
  // - time(nullptr): 这是 <ctime> (或 C 中的 <time.h>) 中的一个标准库函数。
  //   - 参数: 可以是一个指向 time_t 类型的指针。如果参数不是 nullptr，
  //           则该函数也会将获取到的时间值存储到该指针指向的内存位置。
  //   - 返回值: 返回当前的日历时间 (time_t 类型)。
  //   - 如果参数为 nullptr，函数仅返回时间值，不进行额外的存储。
  //   - 如果获取时间失败，函数返回 -1 (并可能设置 errno)。
  time_t curr_time = time(nullptr);
  // 2. 将日历时间转换为本地时间 (Broken-down Time)
  // struct tm *format_time = localtime(&curr_time);
  // - struct tm: 这是一个标准C结构体，用于表示“分解时间”或“细分时间”，
  //              即将日历时间分解为年、月、日、时、分、秒等单独的组成部分。
  //              其主要成员包括：
  //                int tm_sec;   // 秒 (0-60)，60 用于表示闰秒
  //                int tm_min;   // 分 (0-59)
  //                int tm_hour;  // 时 (0-23)
  //                int tm_mday;  // 月中的日 (1-31)
  //                int tm_mon;   // 月 (0-11)，0 代表一月，11 代表十二月
  //                int tm_year;  // 年，自1900年起的年数 (例如，2025年表示为
  //                125) int tm_wday;  // 星期几 (0-6)，0 代表星期日 int
  //                tm_yday;  // 年中的日 (0-365)，0 代表1月1日 int tm_isdst; //
  //                夏令时标志
  //                (正数表示夏令时生效，0表示不生效，负数表示信息不可用)
  // - localtime(const time_t *timer): 标准库函数。
  //   - 参数: 一个指向 time_t 类型值的指针，该值表示要转换的日历时间。
  //   - 返回值: 一个指向静态分配的 `struct tm` 对象的指针。这个 `struct tm`
  //   对象
  //             包含了根据本地时区和夏令时规则转换后的分解时间。
  //   - **重要注意事项 (线程安全和静态存储)**:
  //     `localtime` (以及 `gmtime`) 返回的指针指向一个静态分配的 `struct tm`
  //     对象。 这意味着后续对 `localtime` 或 `gmtime`
  //     的调用可能会覆盖之前调用返回的 `struct tm` 对象的内容。
  //     因此，`localtime`
  //     **不是线程安全的**。如果多个线程同时调用它，可能会发生数据竞争。
  //     在多线程环境中，应使用其可重入版本，如 `localtime_r` (POSIX) 或 C++20
  //     的 `<chrono>` 库。
  //     或者，如果只需要一个线程获取时间，则问题不大，但要小心在信号处理函数中调用它。
  //   - 如果转换失败 (例如 `curr_time` 的值无效)，`localtime` 返回 `nullptr`。
  struct tm *format_time = localtime(&curr_time);
  if (format_time == nullptr)
    return "None";
  // 4. 格式化时间字符串
  // char time_buffer[1024]; //
  // 定义一个足够大的字符数组来存储格式化后的时间字符串 C++ 中更推荐使用
  // std::string 和 std::ostringstream，或者 <chrono> 和 <format> (C++20)
  // 但对于固定格式且长度可控的场景，字符数组和 snprintf
  // 也是一种常见的C风格做法。
  char time_buffer[1024];
  // snprintf(buffer, size, format, ...): C标准库函数，用于安全地格式化字符串。
  // - buffer: 指向存储结果的字符数组的指针。
  // - size: buffer 的大小（包括末尾的空终止符 '\0'）。snprintf 会确保不写入超过
  // size-1 个字符，
  //         并在末尾添加 '\0'，从而防止缓冲区溢出。
  // - format: 格式控制字符串，与 printf 类似。
  // - ...: 与格式字符串中格式说明符对应的附加参数。
  // 返回值:
  // 如果成功，返回将要写入（不包括末尾空字符）的字符数。如果输出被截断（因为size太小），
  //        它返回如果缓冲区足够大时本应写入的字符数。如果发生编码错误，返回负值。
  snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
           format_time->tm_year + 1900, // 年份需要加1900
           format_time->tm_mon + 1,     // 月份从0开始，需要加1
           format_time->tm_mday, format_time->tm_hour, format_time->tm_min,
           format_time->tm_sec);
  // 5. 返回格式化后的时间字符串
  // time_buffer 是一个C风格的字符数组 (以'\0'结尾的字符串)。
  // 将其隐式转换为 std::string 对象并返回。
  return time_buffer;
}

// 全局互斥锁，用于保护日志输出
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

// 2. 日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容

// 核心日志记录函数
// @param filename: 调用日志函数的文件名
// @param line: 调用日志函数的行号
// @param issave: 是否保存到文件
// @param level: 日志级别
// @param format: 格式化字符串
// @param ...: 可变参数列表
void LogMessage(std::string filename, int line, bool issave, int level,
                const char *format, ...) {
  // 获取日志级别字符串
  std::string levelstr = LevelToString(level);
  // 获取当前时间字符串
  std::string timestr = GetTimeString();
  // 获取当前进程ID
  pid_t selfid = getpid();

  // 处理可变参数
  // 1. 定义一个足够大的字符数组缓冲区，用于存储格式化后的日志消息
  // char buffer[1024];
  // - buffer: 一个静态分配的字符数组。它的大小 (1024字节) 决定了单条日志消息
  //           (格式化后的可变参数部分)
  //           的最大长度。如果格式化后的字符串超过这个长度， vsnprintf
  //           会截断输出以防止缓冲区溢出。
  // -
  // 对于非常长的日志消息，这个缓冲区大小可能需要调整，或者考虑使用动态分配的缓冲区
  //   (例如，先用 vsnprintf
  //   计算所需大小，然后分配，再格式化)，但这会增加复杂性。
  char buffer[1024];

  // 2. 声明一个 va_list 类型的变量，用于存储可变参数列表的信息
  // va_list arg;
  // - va_list: 这是 <cstdarg> (或 C 中的 <stdarg.h>) 中定义的一个特殊类型，
  //            用于配合 va_start, va_arg, va_end, va_copy 等宏来访问可变参数。
  //            它通常是一个指针或某种内部结构，具体实现依赖于编译器和平台。
  // - arg: 是 va_list 类型的一个变量，它将用于遍历函数接收到的可变参数。
  va_list arg;

  // 3. 初始化 va_list 变量，使其指向第一个可变参数
  // va_start(arg, format);
  // - va_start: 是一个宏，用于初始化 `arg`。
  //   - 第一个参数 (arg): 是要初始化的 `va_list` 变量。
  //   - 第二个参数 (format): 必须是函数参数列表中最后一个【固定】参数的名称。
  //                        `va_start`
  //                        使用这个固定参数的地址来定位第一个可变参数的位置。
  //                        在本函数 LogMessage(..., const char *format, ...)
  //                        中， `format` 是最后一个固定参数。
  // - 调用 `va_start` 后，`arg` 就准备好被 `va_arg` 或 `vsnprintf`
  // 等函数用来访问
  //   `format` 参数之后的可变参数了。
  // - 注意：必须在访问任何可变参数之前调用 `va_start`。
  va_start(arg, format);

  // 4. 使用 vsnprintf 将格式化字符串和可变参数列表组合成最终的日志消息字符串
  // vsnprintf(buffer, sizeof(buffer), format, arg);
  // - vsnprintf: 是 "variant" (v) "string" (s) "n" (counted) "printf"。
  //            它与 snprintf 类似，但它接受一个 `va_list` (arg) 作为参数，
  //            而不是直接接受可变数量的参数。这使得它非常适合在自定义的可变参数函数中使用。
  //   - 第一个参数 (buffer): 指向存储结果字符串的字符数组的指针。
  //   - 第二个参数 (sizeof(buffer)): `buffer` 的大小（字节数）。`vsnprintf`
  //   会确保
  //                                 写入的字符数（包括末尾的空终止符
  //                                 '\0'）不超过这个大小， 从而防止缓冲区溢出。
  //   - 第三个参数 (format): 格式控制字符串，与 `printf` 的格式字符串相同。
  //                         它包含普通的字符和格式说明符 (如 %s, %d, %f)。
  //   - 第四个参数 (arg): 一个已经由 `va_start` 初始化的 `va_list` 变量，
  //                      它提供了与 `format`
  //                      字符串中格式说明符对应的实际参数值。
  // - 返回值: 如果成功且输出没有被截断，`vsnprintf` 返回将要写入 `buffer`
  // 的字符数
  //          (不包括末尾的空字符 '\0')。
  //          如果输出因为 `buffer` 太小而被截断，它返回如果 `buffer` 足够大时
  //          本应写入的字符数。
  //          如果发生编码错误，它返回一个负值。
  // - 在这里，格式化后的字符串被写入到 `buffer` 中。
  vsnprintf(buffer, sizeof(buffer), format, arg);

  // 5. 清理 va_list 变量，释放其占用的资源
  // va_end(arg);
  // - va_end: 是一个宏，用于在函数处理完所有可变参数后进行必要的清理工作。
  //   - 参数 (arg): 是之前由 `va_start` 初始化的 `va_list` 变量。
  // - 调用 `va_end` 很重要，因为它可能会释放 `va_start`
  // 分配的资源，或者进行其他
  //   平台相关的清理。确保在函数返回前，并且在 `va_list` 不再需要时调用它。
  // - 每次调用 `va_start` 都必须对应一次 `va_end` 的调用。
  va_end(arg);

  // 组装完整的日志消息
  std::string message = "[" + timestr + "]" + "[" + levelstr + "]" + "[" +
                        std::to_string(selfid) + "]" + "[" + filename + "]" +
                        "[" + std::to_string(line) + "] " + buffer + "\n";

  // 使用RAII方式加锁，确保线程安全
  LockGuard lockguard(&lock);

  // 根据issave决定输出目标
  if (!issave) {
    std::cout << message; // 输出到屏幕
  } else {
    SaveFile(logname, message); // 保存到文件
  }
}

// 日志宏定义，简化日志调用
// 使用do-while(0)结构确保宏定义的安全性
// ##__VA_ARGS__ 用于处理可变参数，当没有参数时自动去除逗号
#define LOG(level, format, ...)                                                \
  do {                                                                         \
    LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__);     \
  } while (0)

// 启用文件日志输出
#define EnableFile()                                                           \
  do {                                                                         \
    gIsSave = true;                                                            \
  } while (0)

// 启用屏幕日志输出
#define EnableScreen()                                                         \
  do {                                                                         \
    gIsSave = false;                                                           \
  } while (0)

// 默认传递进来的参数都是整数
// void Test(int num, ...)
// {
//     va_list arg;
//     va_start(arg, num);

//     while(num)
//     {
//         int data = va_arg(arg, int);
//         std::cout << "data: " << data << std::endl;
//         num--;
//     }

//     va_end(arg); // arg = NULL
// }